/**
 * 事务的模式
 * READ：读
 * READ_WRITE：读写
 * VERION_CHANGE: 版本变更
 * @type {{READ: string, READ_WRITE: string, VERION_CHANGE: string}}
 */
const TRANSACTION_MODE = { READ_ONLY: 'readonly', READ_WRITE: 'readwrite', READ_WRITE_FLUSH: 'readwriteflush ', VERION_CHANGE: 'verionchange' };
/*
 * 游标循环的方向
 * CursorDirection.NEXT                     顺序循环
 * CursorDirection.NEXT_NO_DUPLICATE        顺序循环不重复.当取值有重复时，只获取一次。
 * CursorDirection.PREV                     倒序循环
 * CursorDirection.PREV_NO_DUPLICATE        倒序循环不重复.当取值有重复时，只获取一次。
 * @type {{NEXT: *, NEXT_NO_DUPLICATE: *, PREV: *, PREV_NO_DUPLICATE: *}}
 */
window.CURSOR_DIRECTION = { NEXT: IDBCursor.NEXT, NEXT_NO_DUPLICATE: IDBCursor.NEXT_NO_DUPLICATE, PREV: IDBCursor.PREV, PREV_NO_DUPLICATE: IDBCursor.PREV_NO_DUPLICATE };

window.KeyRange = {
    leq: x => DBKeyRange.upperBound(x), //All keys ≤ x
    lt: x => DBKeyRange.upperBound(x, true), //keys < x
    geq: x => DBKeyRange.lowerBound(x),//keys ≥ x
    gt: x => DBKeyRange.lowerBound(x, true), //keys > x
    between: (x, y) => DBKeyRange.bound(x, y),//keys ≥ x && ≤ y
    gt_lt: (x, y) => DBKeyRange.bound(x, y, true, true),//keys > x &&< y
    gt_leq: (x, y) => DBKeyRange.bound(x, y, true, false),//keys > x && ≤ y
    geq_lt: (x, y) => DBKeyRange.bound(x, y, false, true),//keys ≥ x &&< y
    eq: x => DBKeyRange.only(x)//key = x
};

const innerDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
const DBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
const DBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.INDEXDB_STATUES = {
    CONNECT: { ING: 0, SUCCESS: 1, ERROR: 2, CHANGE: 3, CLOSE: 4 },
    DATABASE: { ERROR: 0 }
}

const _instance = (example, type) => {
    let pro = type.prototype
    example = example.__proto__
    while (true) {
        if (example === null)
            return false;
        if (pro === example)
            return true;
        example = example.__proto__;
    }
}

const _copy = (src, target) => {
    if (!target) return src
    for (let prop in src) {
        target[prop] = src[prop]
    }
    return target
}

class Assist {
    static isTrue(assist, error) {
        if (!assist) {
            throw new Error(error)
        }
    }
}

class Database {
    constructor(name, version = 1) {
        Assist.isTrue(name, 'Database name lost')
        this.name = name
        this.version = version
        this.connect = null //数据库连接
        this.events = {
            process: (status) => { }, //数据库过程事件
            success: () => { console.debug("success") },//数据库打开事件
            close: () => { console.debug("close") },//数据库关闭
            change: () => { console.debug("change") },//版本改变
            error: () => { console.debug(arguments) }//数据库打开链接错误或者数据库错误
        }
    }

    drop() {
        this.close();
        let request = innerDB.deleteDatabase(this.name)
        return new Promise((resolve, reject) => {
            request.onerror = function (event) {
                reject(event);
            };
            request.onsuccess = function (event) {
                resolve(event);
            }
        })
    }

    on(event, fn) {
        Assist.isTrue(event && fn, 'Event type and callback lost')
        if (_instance(event, String)) {
            if (event in this.events) {
                this.events[event] = fn
            } else {
                throw new Error(`Unsupport event type ${event}!`)
            }
        } else {
            Object.keys(event).forEach((fn, e) => this.on(e, fn))
        }
        return this
    }

    isOpen() {
        return this.connect != null
    }

    open(name, version) {
        this.name = name = name || this.name
        this.version = version = version || this.version
        const request = innerDB.open(name, version)
        this.events.process(INDEXDB_STATUES.CONNECT.ING)
        request.onerror = (e) => {
            const error = e.target.error
            this.events.process(INDEXDB_STATUES.CONNECT.ERROR)
            this.events.error.call(this, error)
        }
        request.onclose = function (e) {
            this.events.process(INDEXDB_STATUES.CONNECT.CLOSE)
            this.events.close.call(this, e.target.result)
        }
        return new Promise((resolve) => {
            request.onsuccess = (e) => {
                this.connect = e.target.result
                this.events.process(INDEXDB_STATUES.CONNECT.SUCCESS)
                this.connect.onerror = this.connect.onabort = (e) => {
                    const error = e.target.error
                    this.events.process(INDEXDB_STATUES.CONNECT.SUCCESS, INDEXDB_STATUES.DATABASE.ERROR)
                    this.events.error.call(this, error)
                };
                this.events.success.call(this, this.connect)
                resolve(this)
            }
            request.onupgradeneeded = (e) => {
                this.events.process(INDEXDB_STATUES.CONNECT.SUCCESS)
                this.connect = e.target.result
                this.events.change.call(this, this.connect)
                resolve(this)
            }
        });
    }

    close() {
        if (this.isOpen()) {
            this.events.process(INDEXDB_STATUES.CONNECT.CLOSE)
            this.connect.close()
            this.events.close.call(this, this.connect)
            this.connect = null
        }
    }

    /**
     * 只能在open的时候调用
     * @param table {
     *      name:'xx',
     *      primaryKey: 'id',
     *      autoIncrement: true|false，
     *      indexes: [
     *          {name: 'xxx', column: 'id', unique: true|false, multiEntry: true|false},
     *      ],
     *      data: [{}]
     *      }
     */
    createTable(table) {
        Assist.isTrue(table !== undefined, 'Table can not specify')
        if (_instance(table, String)) {
            table = { name: table }
        }
        return new Table(table, this)
    }

    /**
     * 只能在open的时候调用
     * @param tableName
     */
    dropTable(tableName) {
        return this.connect.deleteObjectStore(tableName)
    }

    table(tableName) {
        if (!this.exists(tableName)) {
            throw new Error(`table ${tableName} not exists!`)
        }
        return new Table({ name: tableName }, this)
    }

    exists(table) {
        const tables = this.tables()
        return tables && tables.contains(table)
    }

    tables() {
        return this.connect['objectStoreNames']
    }
}

class Index {
    constructor(name, column, unique = false, multiEntry = false) {
        this.name = name
        this.column = column
        this.unique = !!unique
        this.multiEntry = !!multiEntry
    }
}

const _getObjectStore = (table, mode = TRANSACTION_MODE.READ_ONLY) => {
    Assist.isTrue(_instance(table, Table), "Type error")
    const connect = table.database.connect
    const trans = connect.transaction(table.name, mode)
    trans.oncomplete = (e) => console.debug(e)
    trans.onerror = (e) => console.debug(e)
    return trans.objectStore(table.name)
}

const _request = (request, success) => {
    return new Promise((resolve, reject) => {
        request.onsuccess = (e) => {
            !success ? resolve(e.target.result) : success(e.target.result, resolve)
        }
        request.onerror = (e) => {
            reject(e.target.error, e.target.result)
        }
    })
}

class Table {
    /**
     *
     * @param name 表名
     * @param primaryKey 主键字段
     * @param autoIncrement 是否自动增长
     * @param indexes 表索引 @see TableIndex
     * @param data 表初始化数据
     * @param database 数据库对象 @see Database
     */
    constructor({ name, primaryKey = 'id', autoIncrement = false, indexes = [], data = [] }, database) {
        this.name = name
        this.primaryKey = primaryKey
        this.autoIncrement = !!autoIncrement
        Assist.isTrue(_instance(database, Database) && database.isOpen(), "Database type error")
        this.database = database
        if (!database.exists(name)) {
            const store = database.connect.createObjectStore(name, { keyPath: this.primaryKey, autoIncrement })
            indexes.forEach(({ name, column, unique, multiEntry }) => store.createIndex(name, column, { unique, multiEntry }))
            data.forEach(item => store.add(item))
        }
    }

    /**
     *
     * 插入数据 对象或者数据数组
     * @param data obj or arr
     */
    insert(data) {
        const arr = _instance(data, Array) ? data : [data]
        const store = _getObjectStore(this, TRANSACTION_MODE.READ_WRITE)
        return new Promise((resolve) => {
            let results = [];
            arr.forEach((item, index) => {
                _request(store.add(item)).then(rs => {
                    results.push(rs);
                    if (index + 1 === arr.length) resolve(results);
                });
            })
        })
    }

    /**
     * 当where未指定 直接更新modify
     * @param modify 更新内容 {xxx:xx}
     * @param where 条件 keyRange or key
     */
    update(modify, where) {
        const store = _getObjectStore(this, TRANSACTION_MODE.READ_WRITE);
        if (!where) return _request(store.put(modify));
        else return new Promise((resolve, reject) => {
            _request(store.getAll(where)).then(rs => {
                rs = _instance(rs, Array) ? rs : [rs];
                let rows = [];
                rs.forEach((item, index) => {
                    _request(store.put(_copy(modify, item))).then(r => {
                        rows.push(r);
                        if (index + 1 === rs.length) resolve(rows);
                    });
                })
            }).catch(err => reject(err));
        })
    }

    /**
     * 根据主键值删除
     * @param key
     */
    delete(key) {
        _request(_getObjectStore(this, TRANSACTION_MODE.READ_WRITE).delete(key))
    }

    /**
     * 清空表
     */
    clear() {
        _request(_getObjectStore(this, TRANSACTION_MODE.READ_WRITE).clear())
    }

    /**
     * 根据索引名称删除索引
     * @param name
     */
    dropIndex(name) {
        _getObjectStore(this, TRANSACTION_MODE.READ_WRITE).deleteIndex(name)
    }

    /**
     * 获取指定范围内的主键列表
     * @param keyRange @link keyRange
     * @param limit
     */
    keys(keyRange, limit) {
        Assist.isTrue(_instance(keyRange, DBKeyRange), "")
        const store = _getObjectStore(this)
        const request = store.getAllKeys(keyRange, limit)
        return _request(request)
    }

    /**
     * 统计数量
     * @param keyRange @link keyRange
     */
    count(keyRange) {
        const request = _getObjectStore(this).count(keyRange)
        return _request(request)
    }

    /**
     *
     * @param keyRange A value that is or resolves to an KeyRange.
     * @param limit Specifies the number of values to return if more than one is found.
     * @returns {*}
     */
    query(keyRange, limit) {
        const request = _getObjectStore(this).getAll(keyRange, limit)
        return _request(request)
    }

    queryByKey(key) {
        const request = _getObjectStore(this).get(key)
        return _request(request)
    }

    /**
     *
     * @param keyRange @see KEY_RANGE
     * @param direction @see CURSOR_DIRECTION
     */
    fetch(keyRange, direction) {
        const store = _getObjectStore(this)
        const request = store.openCursor(null, direction)
        return new Promise((resolve, reject) => {
            let rows = [];
            _request(request, (cursor) => {
                if (!cursor) {
                    resolve(rows);
                    return;
                }
                rows.push(cursor.value);
                cursor.continue();
            }).catch(err => reject(err));
        })
    }

    /**
     *
     * @param name indexName
     * @param key key or keyRange
     */
    queryByIndex(name, key) {
        const store = _getObjectStore(this);
        const index = store.index(name)
        return _request(index.getAll(key))
    }

    /**
     * 获取的同时删除 消费掉表内的数据
     * direction: @see CURSOR_DIRECTION
     */
    deplete(direction = CURSOR_DIRECTION.NEXT) {
        const store = _getObjectStore(this, TRANSACTION_MODE.READ_WRITE);
        const request = store.openCursor(null, direction);
        return new Promise((resolve, reject) => {
            let rows = [];
            _request(request, (cursor) => {
                if (!cursor) {
                    resolve(rows);
                    return;
                }
                rows.push(cursor.value);
                cursor.continue();
                store.delete(cursor.key)
            }).catch(err => reject(err));
        })
    }

    /**
     * use index and cursor
     */
    multiple(indexName, keyRange, cursorDirection = CURSOR_DIRECTION.NEXT) {
        const store = _getObjectStore(this)
        const index = store.index(indexName)
        const request = index.openCursor(keyRange, cursorDirection)
        return new Promise((resolve, reject) => {
            let array = [];
            _request(request, cursor => {
                if (!cursor) {
                    resolve(array);
                    return;
                }
                array.push(cursor.value);
                cursor.continue();
            }).catch(err => reject(err));
        })
    }
}

class Driver extends Database {
    insert(table, data) {
        return this.table(table).insert(data)
    }

    select(table, keyRange, limit) {
        return this.table(table).query(keyRange, limit)
    }

    selectByKey(table, key) {
        return this.table(table).queryByKey(key)
    }

    count(table, keyRange) {
        return this.table(table).count(keyRange)
    }

    update(table, modify, where) {
        return this.table(table).update(modify, where)
    }

    delete(table, key) {
        return this.table(table).delete(key)
    }

    truncate(table) {
        return this.table(table).clear()
    }
}


window.Driver = Driver;
module.exports = window.Driver;